Cloud MapとECSを使ったAPI Gatewayでのpreflightリクエスト対応を行う
はじめに
データアナリティクス事業本部のkobayashiです。
ECSで構築したサービスをAPI GatewayのHTTP APIとCloud Mapのプライベート統合で呼び出す仕組みを作っています。その中でAPI GatewayへのアクセスをCognitoで認証したユーザーだけが使えるという制限をかけようとしていたところ、API Gatewayの設定でハマってしまったのでその内容と解決方法をまとめます。
構成図
Cognitoで認証されたユーザーがCognitoで発行されたIdTokenのJWT認証を使ってAPI Gatewayを呼び出します。API GatewayのHTTP APIへ来たリクエストについてはCloud Mapを使ってECSへ流すという仕組みです。またこのAPIはウェブアプリからaxiosで呼び出すことを想定していました。
行った作業とエラーにハマるまで
上記のAPI GatewayとECSやAPI GatewayとCognitoの連携といった仕組自体はAWSアーキテクチャブログや弊社でのブログでも紹介されているように極一般的な仕組みかと思います。
従いまして、これらの記事を参考に以下の作業を行いました。
- ECS,Cloud Map,API Gatewayで認可なしのAPIエンドポイントを作成する
- API Gatewayの認可にCognitoを設定する
- curlコマンドでAPI Gatewayのエンドポイントにリクエストを送る
- ウェブアプリからaxiosでエンドポイントにリクエストを送る
大体想像はつくかと思いますが1-3は特に問題もなく順調に作業を行えましたが、4のaxiosを使った箇所でハマってしまいました。よくaxiosのリクエストの仕様を考えれば簡単に解決できると思うのですが少々手間取ってしまいました。
問題を解決する前のAPI Gatewayの設定
はじめに問題を解決する前のAPI Gatewayの設定を確認します。
API Gatewayの設定
API Gatewayで行った設定は先に紹介したAWSアーキテクチャブログ(Field Notes: Integrating HTTP APIs with AWS Cloud Map and Amazon ECS Services | AWS Architecture Blog )の内容を踏襲して構築したのでAPI Gatewayの中身を見ると以下のようになっています。
これらの設定では任意のメソッド、任意の宛先へのリクエストをECS上のサービスで受け取りレスポンスを返すことができます。
API Gatewayの認可でCognitoを使う設定
あらゆるユーザーがエンドポイントへアクセスしてAPIを使うことができてしまうのでCognitoで認証されたユーザーだけがAPIを使えるように設定を行いました。これは弊社ブログにそのままのエントリー(Cognitoで認証されたユーザーだけがAPI Gatewayを呼び出せるオーソライザーを使ってみた | DevelopersIO )がありますのでこちらを踏襲しています。
Cognitoでの設定をAPI Gatewayでの設定を行うとAPI Gatewayの認可については以下のようになっています。
エンドポイントへのアクセス(curlコマンド)
これでECSで構築したAPIサービスをAPI Gatewayを呼び出す際にCognitoで認証されたユーザーだけがこのAPIを使えるようになります。
試しにcurl
でAPIを叩いてみると問題なくレスポンスが返ってきました。
curl -H GET 'https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/' -H 'Content-Type:application/json;charset=utf-8' -H 'Authorization: Bearer {IdToken}'
HTTP/1.1 200 OK Date: Mon, 14 Jun 2021 01:57:29 GMT Content-Type: application/json Content-Length: 107 Connection: keep-alive server: uvicorn apigw-requestid: A5GddjycNjMEJrA= { "success": true, "message": "成功です" } Response code: 200 (OK); Time: 151ms; Content length: 87 bytes
prefligthによる認証エラー
curlコマンドでAPI Gatewayへのエンドポイントのリクエストに問題がなかったため、次にウェブアプリからaxiosを使って以下のようなコードでリクエストを送るとエラーとなってしまいました。
import axios from "axios"; export const getData = async () => { try { const res = await axios.get( "https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/", { headers: { Authorization: 'Bearer {IdToken}', }, } ); console.log(res.data); } catch (error) { console.log(error); } };
エラー内容を確認してみるとレスポンスとしては以下が返ってきました。
Access to XMLHttpRequest at 'https://xxxx.execute-api.ap-northeast-1.amazonaws.com/param' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
またAP Gatewayのログを見ると以下のエラーが記録されていました。
{ "httpMethod": "OPTIONS", "ip": "xxx.xxx.xxx.xxx", "protocol": "HTTP/1.1", "requestId": "ApWmCgCytjMEJ1w=", "requestTime": "09/Jun/2021:07:17:07 +0000", "responseLength": "26", "routeKey": "$default", "status": "401" }
このエラー内容を見れば分かる通りaxiosでクロスドメインのリソースに対しリクエストを送った際のpreflightの考慮漏れでした。
preflightリクエストは実際のリクエストを送る前にOPTIONS
リクエストを送るのですが、ここまでの作業でのAPI Gatewayの設定ではリクエストの種類に関わらずJWT認証を行っている$default
ルートを経由しているため、preflightのOPTIONS
リクエストではこのJWT認証を回避する必要があリます。
API Gatewayのpreflight対応を行う
対応方法は非常に簡単でAWSのドキュメントにも記載されていますので以下の内容に従ってAPI Gatewayの設定を修正します。
OPTIONS /{proxy+}ルートの追加
OPTIONS
リクエストの場合は認証をしないルートを作成します。
手順1) API GatewayのルートでCreate
を押下し、OPTIONS
を選択し/{proxy+}
を入力してルートの作成をする。
ルートは以下のように$defalut
とOPTIONS /{proxy+}
ルートができます。
手順2) 追加したOPTIONS /{proxy+}
ルートに$defalut
と同じ統合をアタッチする。
手順3) CORSの設定を行う。
今回の設定値はゆるい設定をしてあるので実際にはAccess-Control-Allow-Origin
を適切な値にするなど環境に合わせて設定してください。
認可は$defalut
ルートにはJWT認証、OPTIONS /{proxy+}
はオーソライザーをアタッチしないため、改めての設定は特にありません。
これでAPI Gatewayのpreflight対応が終わりましたので再度ウェブアプリからaxiosを使ってリクエストを送ると正常にcurlコマンドと同じレスポンスが返ってきました。
まとめ
API Gateway + Cloud Map + ECSでのpreflightリクエスト対応を行いました。それぞれのパートでの情報はたくさんありましたがまとまった情報が見つからなかったのでまとめてみました。どなたかのお役に立てれば幸いです。
最後まで読んで頂いてありがとうございました。